package recorder

import (
	"context"
	"fmt"
	"hotgo/internal/dao"
	"hotgo/internal/model/entity"
	"math/rand"
	"os"

	"github.com/gogf/gf/v2/frame/g"
	ffmpeg "github.com/u2takey/ffmpeg-go"
	"golang.org/x/sync/errgroup"
)

// FlagMergeStart
// --  @# 标记开始合并活动个人视频
func (r *sRecorder) FlagMergeStart(ctx context.Context, aid int) error {
	_, err := dao.RecordActivity.Ctx(ctx).Where(dao.RecordActivity.Columns().ActivityId, aid).Data(g.Map{
		dao.RecordActivity.Columns().MergeEnd: 1,
	}).Update()
	return err
}

// flagMergeEnd
// --  @# 标记合并活动个人视频结束
func (r *sRecorder) flagMergeEnd(ctx context.Context, aid int) error {
	_, err := dao.RecordActivity.Ctx(ctx).Where(dao.RecordActivity.Columns().ActivityId, aid).Data(g.Map{
		dao.RecordActivity.Columns().MergeEnd: 2,
	}).Update()
	return err
}

// Merge
// --  @# 个人视频开始合并
func (r *sRecorder) Merge(ctx context.Context, aid int) {
	var total = 0
	var users = make([]entity.ActivityUser, 0)
	err := dao.ActivityUser.Ctx(ctx).Where(dao.ActivityUser.Columns().ActivityId, aid).ScanAndCount(&users, &total, false)
	if nil != err {
		g.Log().Errorf(ctx, "[xldebug][合并视频]:获取指定活动:%d的用户信息时出现DB错误%v", aid, err)
		return
	}
	if 0 == total {
		g.Log().Warningf(ctx, "[xldebug][合并视频]:获取指定活动:%d的用户数量是为0！", aid)
		return
	}

	var eg errgroup.Group
	maxLimit := g.Cfg().MustGet(ctx, "recorder.max_merge_concurrency", 20).Int()
	eg.SetLimit(maxLimit)
	for _, user := range users {
		num := user.Number
		eg.Go(r.mergeWork(ctx, aid, num))
	}
	if err := eg.Wait(); err != nil {
		g.Log().Errorf(ctx, "[xldebug][合并视频]:合并个人视频最终出现错误:%v", err)
	}

	// --  @# 标记合并执行结束
	_ = r.flagMergeEnd(ctx, aid)
}

func (r *sRecorder) mergeWork(ctx context.Context, aid int, userNo string) func() error {
	recorderDatas := make([]entity.RecordOcr, 0)
	recorderCount := 0
	return func() error {
		err := dao.RecordOcr.Ctx(ctx).Where(dao.RecordOcr.Columns().ActivityId, aid).
			Where(dao.RecordOcr.Columns().UserNo, userNo).ScanAndCount(&recorderDatas, &recorderCount, false)
		if nil != err {
			g.Log().Errorf(ctx, "[xldebug][合并视频]:获取指定活动:%d的用户OCR识别信息时出现DB错误%v", aid, err)
			return err
		}
		// --  @# 不存在时
		if 0 == recorderCount {
			return nil
		}
		return r.clipFlv(ctx, recorderDatas)
	}
}

func (r *sRecorder) clipFlv(ctx context.Context, eocr []entity.RecordOcr) error {
	preFrames := g.Cfg().MustGet(ctx, "recorder.pre_frames", 60).Uint64()
	nextFrames := g.Cfg().MustGet(ctx, "recorder.next_frames", 60).Uint64()
	thePath := g.Cfg().MustGet(ctx, "recorder.flv_dir", "/var/www/html/flv_db/").String()
	theActivityId := eocr[0].ActivityId
	theUserNo := eocr[0].UserNo
	theDirPath := fmt.Sprintf("%s%d/v/%s/", thePath, theActivityId, theUserNo)
	_ = os.MkdirAll(theDirPath, os.ModePerm)
	var videoResult = make([]*videoResultPath, 0, len(eocr))
	for _, ocr := range eocr {
		theFrame := ocr.Frame
		var startFrame uint64 = 0
		theFilePath := fmt.Sprintf("%s%d_%d.flv", thePath, ocr.ActivityId, ocr.CameraId)
		if theFrame >= preFrames {
			startFrame = theFrame - preFrames
		}
		endFrame := theFrame + nextFrames
		if rPath, err := r.clipSingleFlv(ctx, theFilePath, startFrame, endFrame, theDirPath, ocr); nil == err {
			videoResult = append(videoResult, &rPath)
		}
	}
	ocrResult, err := r.filterMp4(ctx, videoResult, theDirPath, theActivityId)
	g.Dump(ocrResult)
	return err
}

func (r *sRecorder) clipSingleFlv(ctx context.Context, path string, startFrame uint64, endFrame uint64, dirPath string, ocr entity.RecordOcr) (videoResultPath, error) {
	destFilePath := fmt.Sprintf("%s%s_%d_%d.mp4", dirPath, ocr.UserNo, ocr.ActivityId, ocr.CameraId)
	g.Log().Infof(ctx, "[xldebug][合并视频]:开始剪切个人视频：%s", destFilePath)

	input := ffmpeg.Input(path).Trim(ffmpeg.KwArgs{"start_frame": startFrame, "end_frame": endFrame})

	err := input.SetPts("PTS-STARTPTS").Stream("", "").
		Output(destFilePath).
		OverWriteOutput().
		Run()

	return videoResultPath{
		RealPath:      destFilePath,
		WatermarkPath: "",
	}, err
}

func (r *sRecorder) filterMp4(ctx context.Context, vs []*videoResultPath, dirPath string, aid int) (*videoResultPath, error) {
	theSlideResultMp4 := &videoResultPath{
		RealPath:      "",
		WatermarkPath: "",
	}
	if len(vs) == 1 {
		theSlideResultMp4 = vs[0]
	} else {
		theSlideResultMp4 = r.slideMp4(ctx, vs, dirPath, aid)
	}

	err := r.waterMarkerMp4(ctx, theSlideResultMp4, dirPath, aid)
	return theSlideResultMp4, err

}

func (r *sRecorder) slideMp4(ctx context.Context, vs []*videoResultPath, dirPath string, aid int) *videoResultPath {
	theRealPath := fmt.Sprintf("%s%d_merged.mp4", dirPath, aid)
	theSlideResultMp4 := &videoResultPath{
		RealPath:      theRealPath,
		WatermarkPath: "",
	}

	// --  @# TODO 继续处理视频转场特效
	// --  @# ffmpeg -i 1.mp4 -i 3.mp4 -filter_complex  "[0][1]xfade=transition=circlecrop:duration=1:offset=4,format=yuv420p"  output.mp4

	//
	return theSlideResultMp4
}

func (r *sRecorder) waterMarkerMp4(ctx context.Context, v *videoResultPath, dirPath string, aid int) error {
	theRealPath := fmt.Sprintf("%s%d_%d.mp4", dirPath, aid, rand.Intn(10000))
	theWaterPath := fmt.Sprintf("%s%d.mp4", dirPath, aid)
	_ = os.Rename(v.RealPath, theRealPath)
	v.RealPath = theRealPath
	v.WatermarkPath = theWaterPath
	overlay := ffmpeg.Input("./resource/watermaker/watermaker.png").Filter("scale", ffmpeg.Args{"640:-1"})
	err := ffmpeg.Filter(
		[]*ffmpeg.Stream{
			ffmpeg.Input(theRealPath),
			overlay,
		}, "overlay", ffmpeg.Args{"(W-w)/2:(H-h)/2"}).
		Output(theWaterPath).OverWriteOutput().ErrorToStdOut().Run()
	return err
}
